/*
 * tutorial04.c
 *
 *  Created on: 2011-11-11
 *      Author: gavin
 *      Spawning Threads
 */
#include <stdio.h>

#ifdef __cplusplus  
extern "C" {  
#endif  
	#include "libavformat/avformat.h"
	#include "libavcodec/avcodec.h"
	#include "libswscale/swscale.h"
#ifdef __cplusplus  
}  
#endif

#include <SDL.h>
#include <SDL_thread.h>


#define MAX_AUDIOQ_SIZE       10
#define MAX_VIDEOQ_SIZE        10
#define VIDEO_PICTURE_QUEUE_SIZE    10

#define FF_QUIT_EVENT                   SDL_USEREVENT + 1
#define FF_REFRESH_EVENT            SDL_USEREVENT + 2
#define FF_ALLOC_EVENT                SDL_USEREVENT + 3

#define SDL_AUDIO_BUFFER_SIZE 1024

long rint(double x)
{
 if(x >= 0.0)
	return (long)(x + 0.5);
 else
	return (long)(x - 0.5);
}


SDL_Surface *screen = NULL;

typedef struct PacketQueue {
    AVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;

PacketQueue* audioq = NULL;

void packet_queue_init(PacketQueue *q) {
    memset(q, 0, sizeof(PacketQueue));
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

    AVPacketList *pkt1;
    if (av_dup_packet(pkt) < 0) {
        return -1;
    }
    pkt1 = (AVPacketList *)av_malloc(sizeof(AVPacketList));
    if (!pkt1)
        return -1;
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    SDL_LockMutex(q->mutex);
    if (!q->last_pkt)
        q->first_pkt = pkt1;
    else
        q->last_pkt->next = pkt1;
    q->last_pkt = pkt1;
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);

    SDL_UnlockMutex(q->mutex);
    return 0;
}

int quit = 0;

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);
    for (;;) {

        if (quit) {
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt;
        if (pkt1) {
            q->first_pkt = pkt1->next;
            if (!q->first_pkt)
                q->last_pkt = NULL;
            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            *pkt = pkt1->pkt;
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {
            ret = 0;
            break;
        } else {
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

typedef struct VideoPicture {
    SDL_Overlay *bmp;
    int width, height; /* source height & width */
    int allocated;
} VideoPicture;

typedef struct VideoState {
    AVFormatContext *pFormatCtx;
    int videoStream, audioStream;
    AVStream *audio_st;
    PacketQueue audioq;
    uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
    unsigned int audio_buf_size;
    unsigned int audio_buf_index;
    AVPacket audio_pkt;
    uint8_t *audio_pkt_data;
    int audio_pkt_size;
    AVStream *video_st;
    PacketQueue videoq;

    VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
    int pictq_size, pictq_rindex, pictq_windex;
    SDL_mutex *pictq_mutex;
    SDL_cond *pictq_cond;

    SDL_Thread *parse_tid;
    SDL_Thread *video_tid;

    char filename[1024];
    int quit;
} VideoState;

int queue_picture(VideoState *is, AVFrame *pFrame) {

    VideoPicture *vp;
    int dst_pix_fmt;
    AVPicture pict;

    /* wait until we have space for a new pic */
    SDL_LockMutex(is->pictq_mutex);
    while (is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !is->quit) {
        SDL_CondWait(is->pictq_cond, is->pictq_mutex);
    }
    SDL_UnlockMutex(is->pictq_mutex);

    if (is->quit)
        return -1;

    // windex is set to 0 initially
    vp = &is->pictq[is->pictq_windex];

    /* allocate or resize the buffer!
//     * instead of allocating it here, to avoid locking issues. (I'm still not quite sure why; I believe it's to
     * avoid calling the SDL overlay functions in different threads.)
     * */
    if (!vp->bmp || vp->width != is->video_st->codec->width || vp->height
            != is->video_st->codec->height) {
        SDL_Event event;

        vp->allocated = 0;
        /* we have to do it in the main thread */
        event.type = FF_ALLOC_EVENT;
        event.user.data1 = is;
        SDL_PushEvent(&event);

        /* wait until we have a picture allocated */
        SDL_LockMutex(is->pictq_mutex);
        while (!vp->allocated && !is->quit) {
            SDL_CondWait(is->pictq_cond, is->pictq_mutex);
        }
        SDL_UnlockMutex(is->pictq_mutex);
        if (is->quit) {
            return -1;
        }
    }

    if(vp->bmp) {
        static struct SwsContext *img_convert_ctx;

       SDL_LockYUVOverlay(vp->bmp);

       PixelFormat dst_pix_fmt = PIX_FMT_YUV420P;
       /* point pict at the queue */

       pict.data[0] = vp->bmp->pixels[0];
       pict.data[1] = vp->bmp->pixels[2];
       pict.data[2] = vp->bmp->pixels[1];

       pict.linesize[0] = vp->bmp->pitches[0];
       pict.linesize[1] = vp->bmp->pitches[2];
       pict.linesize[2] = vp->bmp->pitches[1];

       // Convert the image into YUV format that SDL uses
//       img_convert(&pict, dst_pix_fmt,
//           (AVPicture *)pFrame, is->video_st->codec->pix_fmt,
//           is->video_st->codec->width, is->video_st->codec->height);

       img_convert_ctx = sws_getContext( 
		   is->video_st->codec->width, 
		   is->video_st->codec->height ,
           is->video_st->codec->pix_fmt,  
		   is->video_st->codec->width, 
		   is->video_st->codec->height,
           dst_pix_fmt,  SWS_BICUBIC , NULL, NULL, NULL);

       sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize,
               0,  is->video_st->codec->height,  pict.data,  pict.linesize);

       SDL_UnlockYUVOverlay(vp->bmp);
       /* now we inform our display thread that we have a pic ready */
       if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
         is->pictq_windex = 0;
       }
       SDL_LockMutex(is->pictq_mutex);
       is->pictq_size++;
       SDL_UnlockMutex(is->pictq_mutex);
     }
     return 0;
}

void alloc_picture(void *userdata) {

    VideoState *is = (VideoState *) userdata;
    VideoPicture *vp;

    if (!screen) {
        screen = SDL_SetVideoMode(is->video_st->codec->width, is->video_st->codec->height, 0,  0);
        if (!screen) {
            fprintf(stderr, "SDL: could not set video mode - exiting\n");
            exit(1);
        }
    }

    vp = &is->pictq[is->pictq_windex];
    if (vp->bmp) {
        // we already have one make another, bigger/smaller
        SDL_FreeYUVOverlay(vp->bmp);
    }
    // Allocate a place to put our YUV image on that screen
    vp->bmp = SDL_CreateYUVOverlay(is->video_st->codec->width,
            is->video_st->codec->height, SDL_YV12_OVERLAY, screen);
    vp->width = is->video_st->codec->width;
    vp->height = is->video_st->codec->height;

    SDL_LockMutex(is->pictq_mutex);
    vp->allocated = 1;
    SDL_CondSignal(is->pictq_cond);
    SDL_UnlockMutex(is->pictq_mutex);
}

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,  int buf_size) {
    static AVPacket pkt;
    static uint8_t *audio_pkt_data = NULL;
    static int audio_pkt_size = 0;

    int len1, data_size;
    for (;;) {
        while (audio_pkt_size > 0) {
            data_size = buf_size;

            /*
              len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *) audio_buf, &data_size, audio_pkt_data,
             audio_pkt_size);
             */
            len1 = avcodec_decode_audio3(aCodecCtx, (int16_t *) audio_buf,
                    &data_size, &pkt);
            if (len1 < 0) {
                /* if error, skip frame */
                audio_pkt_size = 0;
                break;
            }
            audio_pkt_data += len1;
            audio_pkt_size -= len1;
            if (data_size <= 0) {
                /* No data yet, get more frames */
                continue;
            }
            /* We have data, return it and come back for more later */
            return data_size;
        }
        if (pkt.data)
            av_free_packet(&pkt);

        if (quit) {
            return -1;
        }

        if (packet_queue_get(audioq, &pkt, 1) < 0) {
            return -1;
        }
        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }
}


/**
 * audio callback function
 */
void audio_callback(void *userdata, Uint8 *stream, int len) {

    AVCodecContext *aCodecCtx = ((VideoState *) userdata)->audio_st->codec ;
    int len1, audio_size;

    static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;

    while (len > 0) {
        if (audio_buf_index >= audio_buf_size) {
            /* We have already sent all our data; get more */
            audio_size = audio_decode_frame(aCodecCtx, audio_buf,  sizeof(audio_buf));
             if (audio_size < 0) {
                /* If error, output silence */
                audio_buf_size = SDL_AUDIO_BUFFER_SIZE;
                memset(audio_buf, 0, audio_buf_size);
            } else {
                audio_buf_size = audio_size;
            }
            audio_buf_index = 0;
            printf("audio_size %d\n",  audio_size);
        }
        len1 = audio_buf_size - audio_buf_index;
        if (len1 > len)
            len1 = len;
        memcpy(stream, (uint8_t *) audio_buf + audio_buf_index, len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
}


int video_thread(void *arg) {
    VideoState *is = (VideoState *) arg;
    AVPacket pkt1, *packet = &pkt1;
    int len1, frameFinished;
    AVFrame *pFrame;

    pFrame = avcodec_alloc_frame();

    for (;;) {
        if (packet_queue_get(&is->videoq, packet, 1) < 0) {
            // means we quit getting packets
            break;
        }
       // printf("decode video frame\n");
        // Decode video frame
        len1 = avcodec_decode_video2(is->video_st->codec, pFrame,
                &frameFinished,  packet);
       // printf("decode  video frame+\n");
        // Did we get a video frame?
        if (frameFinished) {
            if (queue_picture(is, pFrame) < 0) {
                break;
            }
        }
        av_free_packet(packet);
    }
    av_free(pFrame);
    return 0;
}

int stream_component_open(VideoState *is, int stream_index) {

    AVFormatContext *pFormatCtx = is->pFormatCtx;
    AVCodecContext *codecCtx;
    AVCodec *codec;
    SDL_AudioSpec wanted_spec, spec;

    if (stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
        return -1;
    }

    // Get a pointer to the codec context for the video stream
    codecCtx = pFormatCtx->streams[stream_index]->codec;

    if (codecCtx->codec_type ==AVMEDIA_TYPE_AUDIO) {
        // Set audio settings from codec info
        wanted_spec.freq = codecCtx->sample_rate;
        wanted_spec.format = AUDIO_S16SYS;
        wanted_spec.channels = codecCtx->channels;
        wanted_spec.silence = 0;
        wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
        wanted_spec.callback = audio_callback;
        wanted_spec.userdata = is;

        if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
            fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
            return -1;
        }
    }
    codec = avcodec_find_decoder(codecCtx->codec_id);
    if (!codec || (avcodec_open(codecCtx, codec) < 0)) {
        fprintf(stderr, "Unsupported codec!\n");
        return -1;
    }

    switch (codecCtx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        is->audioStream = stream_index;
        is->audio_st = pFormatCtx->streams[stream_index];
        is->audio_buf_size = 0;
        is->audio_buf_index = 0;
        memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
        packet_queue_init(&is->audioq);
        SDL_PauseAudio(0);
        break;
    case AVMEDIA_TYPE_VIDEO:
        is->videoStream = stream_index;
        is->video_st = pFormatCtx->streams[stream_index];
        packet_queue_init(&is->videoq);
        is->video_tid = SDL_CreateThread(video_thread, is);
        break;
    default:
        break;
    }
    return 0;
}

int decode_thread(void *data) {
    VideoState *is = (VideoState*) data;
    int videoStream = -1;
    int audioStream = -1;
    int i = 0;
    AVPacket packet;

    av_register_all();

    if (avformat_open_input(&(is->pFormatCtx), is->filename, NULL, NULL) != 0)
        return -1;

    if (av_find_stream_info(is->pFormatCtx) < 0)
        return -1;

    //    dump_format(pFormatCtx, 0, argv[1], 0);
    av_dump_format(is->pFormatCtx, 0, is->filename, 0);

    // Find the first video and audio stream
    for (i = 0; i < is->pFormatCtx->nb_streams; i++) {
        if (is->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO
                && videoStream < 0) {
            videoStream = i;
        }
        if (is->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO
                && audioStream < 0) {
            audioStream = i;
        }
    }

    if (videoStream == -1) {
        fprintf(stderr, "no video stream\n");
        return -1; // Didn't find a video stream
    }

    if (audioStream == -1) {
        fprintf(stderr, "no audio stream\n");
        return -1;
    }

    if (stream_component_open(is, videoStream) != 0 || stream_component_open(
            is, audioStream) != 0) {
        fprintf(stderr, "open video or audio stream failure\n");
        return -1;
    }

    fprintf(stderr, "open video and audio stream \n");

    for (;;) {
        if (is->quit) {
            break;
        }
        // seek stuff goes here
        if (is->audioq.size > MAX_AUDIOQ_SIZE || is->videoq.size
                > MAX_VIDEOQ_SIZE) {
            SDL_Delay(10);
            continue;
        }
        if (av_read_frame(is->pFormatCtx, &packet) < 0) {
			
            if(is->pFormatCtx->pb&&is->pFormatCtx->pb->error)//if (url_ferror(is->pFormatCtx->pb) == 0) 
			{
                SDL_Delay(100); /* no error; wait for user input */
                continue;
            }
			else 
			{
                break;
            }
        }
//        printf( "read one packet\n");
        // Is this a packet from the video stream?
        if (packet.stream_index == is->videoStream) {
            packet_queue_put(&is->videoq, &packet);
        } else if (packet.stream_index == is->audioStream) {
           packet_queue_put(&is->audioq,  &packet);
        } else {
            av_free_packet(&packet);
        }
    }

    while (!is->quit) {
        SDL_Delay(100);
    }

    fail: if (1) {
        SDL_Event event;
        event.type = FF_QUIT_EVENT;
        event.user.data1 = is;
        SDL_PushEvent(&event);
    }
    return 0;
}


void video_display(VideoState *is) {

    SDL_Rect rect;
    VideoPicture *vp;
    AVPicture pict;
    float aspect_ratio;
    int w, h, x, y;
    int i;

    vp = &is->pictq[is->pictq_rindex];
    if (vp->bmp) {
        if (is->video_st->codec->sample_aspect_ratio.num == 0) {
            aspect_ratio = 0;
        } else {
            aspect_ratio = av_q2d(is->video_st->codec->sample_aspect_ratio)
                    * is->video_st->codec->width / is->video_st->codec->height;
        }
        if (aspect_ratio <= 0.0) {
            aspect_ratio = (float) is->video_st->codec->width
                    / (float) is->video_st->codec->height;
        }
        h = screen->h;

        w = ((int) rint(h * aspect_ratio)) & -3;
        if (w > screen->w) {
            w = screen->w;
            h = ((int) rint(w / aspect_ratio)) & -3;
        }
        x = (screen->w - w) / 2;
        y = (screen->h - h) / 2;

        rect.x = x;
        rect.y = y;
        rect.w = w;
        rect.h = h;
        SDL_DisplayYUVOverlay(vp->bmp, &rect);
    }
}

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
    SDL_Event event;
    event.type = FF_REFRESH_EVENT;
    event.user.data1 = opaque;
    SDL_PushEvent(&event);
    return 0; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
    SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}

void video_refresh_timer(void *userdata) {

    VideoState *is = (VideoState *) userdata;
    VideoPicture *vp;

    if (is->video_st) {
        if (is->pictq_size == 0) {
            schedule_refresh(is, 1);
        } else {
            vp = &is->pictq[is->pictq_rindex];
            /* Timing code goes here */

            schedule_refresh(is, 80);

            /* show the picture! */
            video_display(is);

            /* update queue for next picture! */
            if (++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
                is->pictq_rindex = 0;
            }
            SDL_LockMutex(is->pictq_mutex);
            is->pictq_size--;
            SDL_CondSignal(is->pictq_cond);
            SDL_UnlockMutex(is->pictq_mutex);
        }
    } else {
        schedule_refresh(is, 100);
    }
}


VideoState *global_video_state;
int decode_interrupt_cb(void) {
    return (global_video_state && global_video_state->quit);
}

int main(int argc, char *argv[]) {

    SDL_Event event;
    VideoState *is;

    is = (VideoState*)av_mallocz(sizeof(VideoState));

   audioq = &(is->audioq) ;

    strcpy(is->filename, argv[1]);

    is->pictq_mutex = SDL_CreateMutex();
    is->pictq_cond = SDL_CreateCond();

    /*
     * SDL
     */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        exit(1);
    }
    schedule_refresh(is, 40);

    is->parse_tid = SDL_CreateThread(decode_thread, is);
    if (!is->parse_tid) {
        av_free(is);
        return -1;
    }

    for (;;) {
        SDL_WaitEvent(&event);

        switch (event.type) {

        case FF_ALLOC_EVENT:
            alloc_picture(event.user.data1);
            break;

        case FF_REFRESH_EVENT:
          video_refresh_timer(event.user.data1);
          break;
        }
    }
}
